/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.pig.pigpen.editor;

import java.io.File;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.Iterator;
import java.util.List;

import org.apache.pig.pigpen.editor.editparts.PLEditPartFactory;
import org.apache.pig.pigpen.editor.editparts.QueryEditPart;
import org.apache.pig.pigpen.editor.model.Query;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.EditDomain;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.KeyHandler;
import org.eclipse.gef.KeyStroke;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.gef.commands.CommandStackListener;
import org.eclipse.gef.dnd.TemplateTransferDragSourceListener;
import org.eclipse.gef.dnd.TemplateTransferDropTargetListener;
import org.eclipse.gef.palette.PaletteRoot;
import org.eclipse.gef.requests.CreationFactory;
import org.eclipse.gef.requests.SimpleFactory;
import org.eclipse.gef.ui.actions.ActionRegistry;
import org.eclipse.gef.ui.actions.DeleteAction;
import org.eclipse.gef.ui.actions.GEFActionConstants;
import org.eclipse.gef.ui.actions.PrintAction;
import org.eclipse.gef.ui.actions.RedoAction;
import org.eclipse.gef.ui.actions.SaveAction;
import org.eclipse.gef.ui.actions.SelectionAction;
import org.eclipse.gef.ui.actions.StackAction;
import org.eclipse.gef.ui.actions.UndoAction;
import org.eclipse.gef.ui.actions.UpdateAction;
import org.eclipse.gef.ui.actions.WorkbenchPartAction;
import org.eclipse.gef.ui.palette.PaletteViewer;
import org.eclipse.gef.ui.palette.PaletteViewerProvider;
import org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.util.TransferDropTargetListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.SWT;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.EditorPart;

public class PigQueryEditor extends GraphicalEditorWithFlyoutPalette implements
        CommandStackListener, ISelectionListener {

    private PigPenEditor editor;

    private static PaletteRoot PALETTE_MODEL;

    /** the list of action ids that are to EditPart actions */
    private List editPartActionIDs = new ArrayList();

    /** the list of action ids that are to CommandStack actions */
    private List stackActionIDs = new ArrayList();

    /** the list of action ids that are editor actions */
    private List editorActionIDs = new ArrayList();

    /** the editor's action registry */
    private ActionRegistry actionRegistry;

    /** the <code>EditDomain</code> */
    private DefaultEditDomain editDomain;

    private boolean isDirty = false;

    public PigQueryEditor(PigPenEditor editor) {
        this.editor = editor;

        editDomain = new DefaultEditDomain(this);
        setEditDomain(editDomain);
    }

    protected void configureGraphicalViewer() {
        super.configureGraphicalViewer();

        GraphicalViewer viewer = getGraphicalViewer();
        viewer.setEditPartFactory(new PLEditPartFactory());
        viewer.setKeyHandler(getCommonKeyHandler());
        // new GraphicalViewerKeyHandler(viewer));
    }

    protected void initializeGraphicalViewer() {
        IEditorInput input = getEditorInput(); // the file being opened, or null
        // if this is a new editor window
        // (I think!)

        QueryEditPart query = new QueryEditPart(this);
        getGraphicalViewer().setContents(query);

        if (input != null) {
            try {
                query.loadFile(input);
            } catch (Throwable e) {
                query = new QueryEditPart(this);
                getGraphicalViewer().setContents(query);
            }
        }

        File file = ((Query) query.getModel()).getFile();
        if (file != null)
            this.setPartName(file.getName());

        getGraphicalViewer().addDropTargetListener(
                createTransferDropTargetListener());
    }

    public void refreshHeaderPage() {
        editor.refreshHeaderPage();
    }

    /**
     * Create a transfer drop target listener. When using a
     * CombinedTemplateCreationEntry tool in the palette, this will enable model
     * element creation by dragging from the palette.
     * 
     * @see #createPaletteViewerProvider()
     */
    private TransferDropTargetListener createTransferDropTargetListener() {
        return new TemplateTransferDropTargetListener(getGraphicalViewer()) {
            protected CreationFactory getFactory(Object template) {
                return new SimpleFactory((Class) template);
            }
        };
    }

    public void doSave(IProgressMonitor monitor) {
        getQuery().save(monitor);
        setDirty(false);
    }

    public void gotoMarker(IMarker marker) {
        // TODO ...
    }

    public boolean isSaveAsAllowed() {
        return false; // TODO: will want to enable Save-As later
    }

    // public void doSaveAs() {
    // TODO: later, when we enable Save-As, need to implement this method
    // setDirty(false);
    // }

    Query getQuery() {
        GraphicalViewer viewer = getGraphicalViewer();
        if (viewer == null)
            return null;

        EditPart editPart = viewer.getContents();
        if (editPart == null)
            return null;

        return (Query) editPart.getModel();
    }

    // ------------------------

    /**
     * Initializes the editor.
     */
    public void init(IEditorSite site, IEditorInput input)
            throws PartInitException {
        // store site and input
        setSite(site);
        setInput(input);

        // add CommandStackListener
        getCommandStack().addCommandStackListener(this);

        // add selection change listener
        getSite().getWorkbenchWindow().getSelectionService()
                .addSelectionListener(this);

        // initialize actions
        createActions();

    }

    /** the selection listener implementation */
    public void selectionChanged(IWorkbenchPart part, ISelection selection) {
        updateActions(editPartActionIDs);
    }

    /**
     * The <code>CommandStackListener</code> that listens for
     * <code>CommandStack </code> changes.
     */
    /*
     * public void commandStackChanged(EventObject event) {
     * 
     * updateActions(stackActionIDs); setDirty(getCommandStack().isDirty()); }
     */

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.gef.ui.parts.GraphicalEditor#commandStackChanged(java.util
     * .EventObject)
     */
    public void commandStackChanged(EventObject event) {
        setDirty(getCommandStack().isDirty());
        super.commandStackChanged(event);
    }

    /**
     * Indicates if the editor has unsaved changes.
     * 
     * @see EditorPart#isDirty
     */
    public boolean isDirty() {
        return isDirty;
    }

    public void setDirty(boolean isDirty) {
        this.isDirty = isDirty;
        firePropertyChange(IEditorPart.PROP_DIRTY);
    }

    public void dispose() {
        // remove CommandStackListener
        getCommandStack().removeCommandStackListener(this);
        // remove selection listener
        getSite().getWorkbenchWindow().getSelectionService()
                .removeSelectionListener(this);
        // dispos3 the ActionRegistry (will dispose all actions)
        getActionRegistry().dispose();
        // important: always call super implementation of dispose
        super.dispose();
    }

    /**
     * Adaptable implementation for Editor
     */
    public Object getAdapter(Class adapter) {
        // we need to handle common GEF elements we created
        if (adapter == GraphicalViewer.class || adapter == EditPartViewer.class)
            return getGraphicalViewer();
        else if (adapter == CommandStack.class)
            return getCommandStack();
        else if (adapter == EditDomain.class)
            return getEditDomain();
        else if (adapter == ActionRegistry.class)
            return getActionRegistry();

        // the super implementation handles the rest
        return super.getAdapter(adapter);
    }

    /**
     * Returns the <code>CommandStack</code> of this editor's
     * <code>EditDomain</code>.
     * 
     * @return the <code>CommandStack</code>
     */
    public CommandStack getCommandStack() {
        return getEditDomain().getCommandStack();
    }

    protected KeyHandler getCommonKeyHandler() {

        KeyHandler sharedKeyHandler = new KeyHandler();
        sharedKeyHandler.put(KeyStroke.getPressed(SWT.DEL, 127, 0),
                getActionRegistry().getAction(GEFActionConstants.DELETE));
        sharedKeyHandler.put(KeyStroke.getPressed(SWT.F2, 0),
                getActionRegistry().getAction(GEFActionConstants.DIRECT_EDIT));

        return sharedKeyHandler;
    }

    /**
     * Creates actions and registers them to the ActionRegistry.
     */
    protected void createActions() {
        addStackAction(new UndoAction(this));
        addStackAction(new RedoAction(this));
        addEditPartAction(new DeleteAction((IWorkbenchPart) this));
        addEditorAction(new SaveAction(this));
        addEditorAction(new PrintAction(this));
    }

    /**
     * Adds an <code>EditPart</code> action to this editor.
     * 
     * <p>
     * <code>EditPart</code> actions are actions that depend and work on the
     * selected <code>EditPart</code>s.
     * 
     * @param action
     *            the <code>EditPart</code> action
     */
    protected void addEditPartAction(SelectionAction action) {
        getActionRegistry().registerAction(action);
        editPartActionIDs.add(action.getId());
    }

    /**
     * Adds an <code>CommandStack</code> action to this editor.
     * 
     * <p>
     * <code>CommandStack</code> actions are actions that depend and work on the
     * <code>CommandStack</code>.
     * 
     * @param action
     *            the <code>CommandStack</code> action
     */
    protected void addStackAction(StackAction action) {
        getActionRegistry().registerAction(action);
        stackActionIDs.add(action.getId());
    }

    /**
     * Adds an editor action to this editor.
     * 
     * <p>
     * <Editor actions are actions that depend and work on the editor.
     * 
     * @param action
     *            the editor action
     */
    protected void addEditorAction(WorkbenchPartAction action) {
        getActionRegistry().registerAction(action);
        editorActionIDs.add(action.getId());
    }

    /**
     * Adds an action to this editor's <code>ActionRegistry</code>. (This is a
     * helper method.)
     * 
     * @param action
     *            the action to add.
     */
    protected void addAction(IAction action) {
        getActionRegistry().registerAction(action);
    }

    /**
     * Updates the specified actions.
     * 
     * @param actionIds
     *            the list of ids of actions to update
     */
    protected void updateActions(List actionIds) {
        for (Iterator ids = actionIds.iterator(); ids.hasNext();) {
            IAction action = getActionRegistry().getAction(ids.next());
            if (null != action && action instanceof UpdateAction)
                ((UpdateAction) action).update();

        }
    }

    /**
     * Returns the action registry of this editor.
     * 
     * @return the action registry
     */
    protected ActionRegistry getActionRegistry() {
        if (actionRegistry == null)
            actionRegistry = new ActionRegistry();

        return actionRegistry;
    }

    /*
	 */
    protected void firePropertyChange(int propertyId) {
        super.firePropertyChange(propertyId);
        updateActions(editorActionIDs);
    }

    @Override
    protected PaletteRoot getPaletteRoot() {
        if (PALETTE_MODEL == null)
            PALETTE_MODEL = PLEditorPaletteFactory.createPalette();
        return PALETTE_MODEL;
    }

    protected PaletteViewerProvider createPaletteViewerProvider() {
        return new PaletteViewerProvider(getEditDomain()) {
            protected void configurePaletteViewer(PaletteViewer viewer) {
                super.configurePaletteViewer(viewer);
                // create a drag source listener for this palette viewer
                // together with an appropriate transfer drop target listener,
                // this will enable
                // model element creation by dragging a
                // CombinatedTemplateCreationEntries
                // from the palette into the editor
                // @see ShapesEditor#createTransferDropTargetListener()
                viewer
                        .addDragSourceListener(new TemplateTransferDragSourceListener(
                                viewer));
            }
        };
    }

}